package be.kuleuven.cs.mop.gui.logging;


import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.Rectangle;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.LogRecord;

import javax.swing.AbstractListModel;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JTextArea;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;



/**
 * Displays log entries in a JList
 * @author Ruben Feyen
 */
public class LogList extends JList {
	private static final long serialVersionUID = 1L;
	
	private static final Renderer RENDERER = new Renderer();
	
	private final ListModel	model	= new ListModel(this);
	private final Queue		queue	= new Queue(model);
	
	
	public LogList() {
		setCellRenderer(RENDERER);
		setModel(model);
		setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
		
		final Thread t = new Thread(queue, "LogListQueue");
		t.setDaemon(true);
		t.start();
	}
	
	
	
	/**
	 * Clears all entries from the <code>LogList</code>
	 */
	public final void clear() { model.clear(); }
	
	/**
	 * Terminates the operation of the <code>LogList</code> & clears all data
	 */
	public final void destroy() {
		queue.running = false;
		queue.notify();
		
		clear();
	}
	
	/**
	 * Logs the specified <code>LogRecord</code>
	 * @param logRecord - a <code>LogRecord</code>
	 */
	public final boolean log(final LogRecord logRecord) {
		return queue.offer(new Entry(logRecord));
	}
	
	
	
	
	
	
	
	
	
	
	private static final class Entry {
		
		private static final Formatter FORMATTER = new Formatter();
		
		private final LogRecord record;
		private final String	text;
		
		
		private Entry(final LogRecord record) {
			text = FORMATTER.format(this.record = record);
		}
		
		
		
		@Override
		public String toString() {
			//	Required for copy/paste functionality
			return text;
		}
		
	}
	
	
	
	
	
	private static final class Formatter extends java.util.logging.Formatter {
		
		private static final DateFormat	DATE	= new SimpleDateFormat("hh:mm:ss");
//		private static final int		LENGTH	= 15;
//		private static final String		FORMAT	= ("[ %-" + LENGTH + "s ]   ");
//		private static final String		SUB		= "...";
		
		
		
		@Override
		public final String format(final LogRecord record) {
			return new StringBuilder(64).append('[')
			.append(DATE.format(record.getMillis())).append("]   ")
//			.append(String.format(FORMAT, getLoggerName(record)))
			.append(record.getMessage()).append(' ')
			.append(LogUtils.format(record.getThrown()))
			.toString();
		}
		
//		private static final String getLoggerName(final LogRecord record) {
//			final String name = LogUtils.getLoggerName(record);
//			return (name.length() > LENGTH) ?
//				(name.substring(0, (LENGTH - SUB.length())) + SUB) : name;
//		}
		
	}
	
	
	
	
	
	private static final class ListModel extends AbstractListModel {
		private static final long serialVersionUID = 1L;
		
		private static final Rectangle	BOTTOM		=
			new Rectangle(0, Integer.MAX_VALUE, 0, 0);
		private static final int		MAX_ENTRIES	= 300;
		
		private final Runnable scroll;
		
		private List<Entry> records = new ArrayList<Entry>(MAX_ENTRIES);
		
		
		private ListModel(final LogList list) {
			scroll = new Runnable() {
				@Override
				public final void run() { list.scrollRectToVisible(BOTTOM); }
			};
		}
		
		
		
		private final void add(final Entry e) {
			synchronized (this) {
				if (records.add(e)) {
					trim();
					fireIntervalAdded(this, getSize(), getSize());
					SwingUtilities.invokeLater(scroll);
				}
			}
		}
		
		private final void clear() {
			synchronized (this) {
				records.clear();
				fireIntervalRemoved(this, 0, 0);
			}
		}
		
		@Override
		public final Object getElementAt(final int index) { return records.get(index); }
		
		@Override
		public final int getSize() { return records.size(); }
		
		private final void trim() {
			final int SIZE = getSize();
			if (SIZE >= MAX_ENTRIES)
				records = records.subList((SIZE - MAX_ENTRIES), SIZE);
		}
		
	}
	
	
	
	
	
	private static final class Queue extends LinkedBlockingQueue<Entry> implements Runnable {
		private static final long serialVersionUID = 1L;
		
		private final ListModel	model;
		
		private boolean running = true;
		
		
		private Queue(final ListModel model) {
			this.model = model;
		}
		
		
		
		@Override
		public final void run() {
			Entry entry;
			while (running) {
				try {
					if ((entry = take()) != null)
						model.add(entry);
					Thread.sleep(15);
				} catch (final InterruptedException ignored) {}
			}
		}
		
	}
	
	
	
	
	
	private static final class Renderer extends JTextArea implements ListCellRenderer {
		private static final long serialVersionUID = 1L;
		
		private static final JLabel	EMPTY	= new JLabel();
		private static final Font	FONT	= new Font(Font.MONOSPACED, Font.PLAIN, 12);
		
		
		
		@Override
		public final Border getBorder() { return null; }
		
		@Override
		public final Font getFont() { return FONT; }
		
		@Override
		public Component getListCellRendererComponent(final JList list,
				final Object value, final int index,
				final boolean isSelected, final boolean cellHasFocus) {
			if (!(value instanceof Entry))
				return EMPTY;
			
			final Entry entry = (Entry)value;
			
			setText(entry.text);
//			setComponentOrientation(list.getComponentOrientation());
			
			if (cellHasFocus || isSelected) {
				setBackground(Color.BLUE);
				setForeground(Color.WHITE);
			} else {
				final Level lvl = entry.record.getLevel();
				if (lvl == Level.WARNING) {
					setForeground(Color.RED);
				} else if (lvl == Level.SEVERE) {
					setBackground(Color.RED);
					setForeground(Color.WHITE);
				} else if (lvl == Level.CONFIG) {
					setBackground(Color.BLACK);
					setForeground(Color.WHITE);
				} else { // standard style
					setBackground(Color.WHITE);
					setForeground(Color.DARK_GRAY);
				}
			}
			
			return this;
		}
		
		@Override
		public final boolean isFocusable() { return false; }
		
	}
	
}